package funcSless

import (
	"context"
	"errors"
	"sort"

	"gitee.com/wchpub/fnsvr"
	"github.com/samber/lo"
)

//函数调用上下文
//玩家锁
//管理本次调用的缓存数据
//档案存取
//档案脏标记管理

var ErrRepeatedlyCallingLockPlayers = errors.New("funcContext: repeatedly calling LockPlayers")
var ErrNoLockedPlayer = errors.New("funcContext: no locked player")

type Context struct {
	plyLock playerLock
	players map[string]*PlayerData
}

// 锁定玩家，每个上下文在一次函数调用中仅可调用一次，多次调用锁定尚未锁定的玩家会报错
func (m *Context) LockPlayers(playerIds ...string) error {

	if len(m.players) > 0 { //已经锁定过
		//避免重复锁定导致ab死锁
		for _, id := range playerIds {
			_, ok := m.players[id]
			if !ok { //新增的玩家锁
				return ErrRepeatedlyCallingLockPlayers
			}
		}

		//没有新增，则已经全部被锁定，直接返回nil
		return nil
	}

	//按字典序排列玩家锁，避免ab死锁
	uniqIds := lo.Uniq(playerIds)
	sort.Strings(uniqIds)
	for _, id := range uniqIds {
		if err := m.plyLock.lock(gFuncSlessEnv.LockMaxWait, id); err != nil {

			//解锁全部，并清空已锁定
			m.plyLock.unLock()
			m.players = make(map[string]*PlayerData)

			return err
		}

		newPly := newPlayerData(id)
		m.players[id] = newPly
	}
	return nil

}

// 保存所有脏数据，并释放所有玩家锁，重置后应丢弃函数内缓存的所有数据，读取数据必须从上下文内重新读取
func (m *Context) Reset() {
	m.EndCall()
	m.players = make(map[string]*PlayerData)
}

// 获取玩家数据
func (m *Context) GetPlayerData(playerId string) (*PlayerData, error) {
	re, ok := m.players[playerId]
	if !ok {
		return nil, ErrNoLockedPlayer
	}
	return re, nil
}

func (m *Context) EndCall() {
	ctx := context.Background()
	pipe := gFuncSlessEnv.Redis.Pipeline()
	changedBlocks := make(map[string]int64)

	//增量存档到redis
	for _, plyData := range m.players {
		plyData.save(&pipe, changedBlocks)
	}

	//批量写入脏数据标记
	if len(changedBlocks) > 0 {
		_, err := fnsvr.BatchAtomicIncreaseHashFields(pipe, REDIS_BLOCK_DIRTY_KEY, changedBlocks)
		if err != nil {
			fnsvr.Log().Error("EndCall save dirty flag error", "err", err)
		}
	}

	// 执行
	cmds, err := pipe.Exec(ctx)
	if err != nil {
		fnsvr.Log().Error("EndCall redis exec error", "err", err)
		for _, cmd := range cmds {
			if cmd.Err() != nil {
				fnsvr.Log().Error("EndCall redis exec error", "cmd", cmd, "err", cmd.Err())
			}
		}
	}

	// 释放玩家锁
	m.plyLock.unLock()
}

// 创建函数调用上下文
func NewContext() *Context {
	re := &Context{
		players: make(map[string]*PlayerData),
	}
	initPlayerLock(gFuncSlessEnv.Redis, &re.plyLock)

	return re
}
